/** @file bell.c
 *
 * @author cameris camerisdump@gmail.com
 */

#include "bell.h"
#include "working_timer.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

/** @brief tone buffer */
struct tone {
	uint8_t ocr;
	uint8_t duration;
};

/** @name tone ringbuffer variables
 * @{ */
static volatile uint_fast8_t bell_bufsize;
static uint_fast8_t bell_bufout;
static uint_fast8_t bell_bufin;
static struct tone bell_buf[BELL_BUF_SIZE];
/**@} */

static uint_fast8_t duration = 0;
static uint_fast8_t bell_enabled = 0;

#define BELL_SET_PWM()	BELL_TIMERB |= (1<<BELL_CS2)|(1<<BELL_CS0); \
						BELL_TIMERA |= (1<<BELL_COM0);

#define BELL_CLR_PWM()	BELL_TIMERB &= ~((1<<BELL_CS2)|(1<<BELL_CS1)|(1<<BELL_CS0)); \
						BELL_TIMERA &= ~((1<<BELL_COM1)|(1<<BELL_COM0)); BELL_PORT &= ~(1<<BELL);

/* tone ocr list {{{ */
const __flash uint8_t tone_ocrs[] = {
	0, 148, 140, 132, 125, 118, 111, 105, 99, 93, 88, 83, 78, 74,
	69, 66, 62, 58, 55, 52, 49, 46, 43, 41, 39, 36, 34, 32, 30,
	29, 27, 25, 24, 23, 21, 20, 19, 18,
}; /* }}} */

/** @brief bell duration counter */
static void bell_duration_cnt(void)
{ /* {{{ */
	static uint_fast8_t bellcnt = 0;
	bellcnt++;
	if (duration <= bellcnt) {
		bellcnt = 0;
		if (bell_bufsize == 0) {
			if (duration != 0) BELL_CLR_PWM();
			duration = 0;
		} else {
			duration = bell_buf[bell_bufout].duration;
			BELL_OCR = bell_buf[bell_bufout].ocr;

			if ( ++bell_bufout >= BELL_BUF_SIZE ) {
				bell_bufout = 0;
			}
			bell_bufsize--;
			BELL_SET_PWM();
		}
	}
} /* }}} */

/** @brief initialize bell */
void bell_init(void)
{ /* {{{ */
	BELL_DDR |= (1<<BELL);
	BELL_PORT &= ~(1<<BELL);
	BELL_TIMERA |= (0<<BELL_WGM1)|(1<<BELL_WGM0)|(0<<BELL_COM1)|(1<<BELL_COM0);
	BELL_TIMERB |= (1<<BELL_WGM2);
	wtimer_add(&bell_duration_cnt);
} /* }}} */

/** @brief add a tone to buffer
 * @param tone tone to play
 * @param duration duration in (1/WTIMER_MS)ms */
void bell_add_tone(enum tones tone, uint_fast8_t duration)
{ /* {{{ */
	if (tone >= BELL_MAXTONES) return;
	NONATOMIC_BLOCK(NONATOMIC_RESTORESTATE) {
		while(bell_bufsize >= BELL_BUF_SIZE) {};
	}
	ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
		bell_buf[bell_bufin].ocr = tone_ocrs[tone];
		bell_buf[bell_bufin].duration = duration;
		if (++bell_bufin >= BELL_BUF_SIZE) {
			bell_bufin = 0;
		}
		bell_bufsize++;
	}
	/* enable pwm */
	BELL_SET_PWM();
} /* }}} */

/** @brief enable/disable bell 
 * @param b bool 1 = ON, 0 = OFF */
void bell_enable(uint_fast8_t b)
{
	if (b != 0) b = 1;
	bell_enabled = b;
}

/** @brief get state of enable bell */
uint_fast8_t bell_get_enable(void)
{
	return bell_enabled;
}

/* vim:set foldmethod=marker: */
